import * as Cesium from "cesium";
import { ImprovedNoise } from "three/addons/math/ImprovedNoise.js";
import Texture3D from "@/utils/texture3D.js";
import * as dat from "dat.gui";

const vertexShaderSource = `
    in float batchId;
    in vec3 position;
    out vec3 vOrigin;
    out vec3 vDirection;

    void main() {
      // 相机在世界坐标中的位置
      vOrigin = czm_encodedCameraPositionMCHigh + czm_encodedCameraPositionMCLow;
      // 计算从相机到当前顶点（云包围体体素的某点）的方向向量 vDirection
      vDirection = position - vOrigin;
      gl_Position = czm_projection * czm_modelView * vec4(position, 1.0);
    }
`;

const fragmentShaderSource = `
    // #version 300 es
    precision mediump sampler3D;
    #define epsilon 0.0001
    uniform sampler3D u_cloudNoise3D;
    in vec3 vOrigin;
    in vec3 vDirection;
    out vec4 color;
    uniform float u_cloudRadius;
    uniform float threshold;
    uniform float steps;

    // 使用AABB算法做射线与包围盒的相交检测
    // 用于判断从相机发出的射线是否穿过一个立方体边界（即云的包围盒），返回相交区间 [t0, t1]
    // 返回的是射线参数t，代表射线从起点穿过立方体的起止点
    vec2 hitBox( vec3 orig, vec3 dir ) {
      const vec3 box_min = vec3( - 250.0 );
      const vec3 box_max = vec3( 250.0 );
      vec3 inv_dir = 1.0 / dir;
      vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
      vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
      vec3 tmin = min( tmin_tmp, tmax_tmp );
      vec3 tmax = max( tmin_tmp, tmax_tmp );
      float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
      float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
      return vec2( t0, t1 );
    }

    // 从三维噪声纹理中读取当前点的密度值（用于模拟云的厚度）
    // 云体积是通过 3D 噪声控制其密度分布的，密度值决定该点是否有云
    float sample1( vec3 p ) {
      return texture( u_cloudNoise3D, p ).r;
    }

    // 计算云表面法线
    // 根据密度的局部梯度估计当前点的法线方向，用于实现光照或颜色调节
    // 使用中心差分法计算三维梯度（x, y, z 方向密度差异）
    vec3 normal( vec3 coord ) {
      if ( coord.x < epsilon ) return vec3( u_cloudRadius, 0.0, 0.0 );
        if ( coord.y < epsilon ) return vec3( 0.0, u_cloudRadius, 0.0 );
        if ( coord.z < epsilon ) return vec3( 0.0, 0.0, u_cloudRadius );
        if ( coord.x > u_cloudRadius - epsilon ) return vec3( - u_cloudRadius, 0.0, 0.0 );
        if ( coord.y > u_cloudRadius - epsilon ) return vec3( 0.0, - u_cloudRadius, 0.0 );
        if ( coord.z > u_cloudRadius - epsilon ) return vec3( 0.0, 0.0, - u_cloudRadius );
        float step = 0.01;
        float x = sample1( coord + vec3( - step, 0.0, 0.0 ) ) - sample1( coord + vec3( step, 0.0, 0.0 ) );
        float y = sample1( coord + vec3( 0.0, - step, 0.0 ) ) - sample1( coord + vec3( 0.0, step, 0.0 ) );
        float z = sample1( coord + vec3( 0.0, 0.0, - step ) ) - sample1( coord + vec3( 0.0, 0.0, step ) );
        return normalize( vec3( x, y, z ) );
      }
        
      void main(){
        vec3 rayDir = normalize( vDirection );
        // 1，相交检测，hitBox 返回射线穿过体积的 t 值区间
        vec2 bounds = hitBox( vOrigin, rayDir );
        if ( bounds.x > bounds.y ) discard;
        bounds.x = max( bounds.x, 0.0 );
        // 2，计算步进量
        // 初始化射线起始点位，p是射线进入包围盒的第一个点（起点）的位置
        vec3 p = vOrigin + bounds.x * rayDir;
        // 获取每个方向的单位变化比inc
        // inc是一个用于辅助计算的值，代表在各个方向上单位距离前进时，相应坐标变化的速率
        // 在每个轴上的 inc 是步长与方向的比值（倒数），用于评估在每个轴方向上，单位距离内相交的 voxel 数量或间隔
        vec3 inc = 1.0 / abs( rayDir );
        // 选择 inc 中的最小值来作为基础步长，即选择在最短方向上前进一步所需的距离
        // 为了保证光线以比较平滑的方式穿过体素（volume voxel），而不会跳过可能存在云密度的区域
        float delta = min( inc.x, min( inc.y, inc.z ) );
        // 步进长度 delta 除以一个用户设置的 steps 参数，进一步减小每次步进的距离
        // steps 是控制精度与性能的关键参数，越大步长越小，渲染越精细但性能开销越大
        delta /= steps;
        // 3，射线步进采样
        for ( float t = bounds.x; t < bounds.y; t += delta ) {
          // 沿射线前进，采样点密度值
          float d = sample1( p + 0.5 );
          // 若某点密度高于 threshold，即该点为云的体素
          if ( d > threshold ) {
            // 计算其颜色与法线用于简单光照
            color.rgb = normal( p + 0.5 ) * 0.5 + ( p * 1.5 + 0.25 );
            // 设置 alpha 不透明，渲染该点
            color.a = 1.;
            break;
          }
          // 前进射线点位
          p += rayDir * delta;
        }
        // 如果整个射线未击中任何密度值大于阈值的点，则 discard
        if ( color.a == 0.0 ) discard;
      }
`;
// 全球体积云Primitive
export default class CloudPrimitive {
  constructor(options) {
    this.drawCommand = undefined;
    this._noiseTexture = undefined;
    if (Cesium.defined(options)) {
      this.modelMatrix = options.modelMatrix;
      this.geometry = options.geometry;
      this.data = options.data;
      this.halfdim = new Cesium.Cartesian3();
      Cesium.Cartesian3.divideByScalar(options.dim, 2, this.halfdim);
    }
  }

  generate3DNoiseTexture(context) {
    const size = 64;
    const data = new Float32Array(size * size * size * 4); // 改用浮点数组存储0-1值
    const maxD = Math.sqrt(3) * (size / 2); // 最大距离（对角线）
    const scale = 0.05;
    const perlin = new ImprovedNoise();
    let i = 0;
    let minNoise = Infinity;
    let maxNoise = -Infinity;

    for (let a = 0; a < 4; a++) {
      for (let z = 0; z < size; z++) {
        for (let y = 0; y < size; y++) {
          for (let x = 0; x < size; x++) {
            // 计算相对坐标和欧氏距离
            const dx = x - size / 2;
            const dy = y - size / 2;
            const dz = z - size / 2;
            const d = Math.sqrt(dx * dx + dy * dy + dz * dz);

            // 归一化距离到0-1范围
            const dNormalized = Cesium.Math.clamp(d / maxD, 0.01, 1.0);

            // 计算Perlin噪声值并映射到0-1
            const noiseVal =
              (perlin.noise(x * scale, y * scale, z * scale) + 1) * 0.5;

            // 计算最终值：噪声控制亮度，距离控制衰减
            const value = (noiseVal * 0.5 + 0.5) * dNormalized * 0.99 + 0.01; // 0-1范围

            minNoise = Math.min(minNoise, value);
            maxNoise = Math.max(maxNoise, value);
            // 写入浮点数据（RGBA四个通道）
            data[i++] = value;
          }
        }
      }
    }
    console.log("Noise min:", minNoise, "max:", maxNoise);

    const texture_size = Math.ceil(Math.sqrt(data.length));

    const texture = new Texture3D({
      context: context,
      width: size,
      height: size,
      depth: size,
      flipY: false,
      pixelFormat: Cesium.PixelFormat.RGBA,
      pixelDataType: Cesium.PixelDatatype.FLOAT,
      source: {
        width: size,
        height: size,
        arrayBufferView: data,
      },
      sampler: new Cesium.Sampler({
        minificationFilter: Cesium.TextureMinificationFilter.LINEAR,
        magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR,
      }),
    });

    return texture;
  }

  createDrawCommand(context) {
    const sphere = Cesium.BoxGeometry.fromDimensions({
      vertexFormat: Cesium.VertexFormat.POSITION_AND_ST,
      dimensions: new Cesium.Cartesian3(700, 700, 700),
    });
    const geometry = Cesium.BoxGeometry.createGeometry(sphere);
    const attributelocations =
      Cesium.GeometryPipeline.createAttributeLocations(geometry);
    if (!this._noiseTexture) {
      this._noiseTexture = this.generate3DNoiseTexture(context);
    }
    this.vertexarray = Cesium.VertexArray.fromGeometry({
      context: context,
      geometry: geometry,
      attributes: attributelocations,
    });
    const renderstate = Cesium.RenderState.fromCache({
      depthTest: {
        enabled: true,
      },
      blending: Cesium.BlendingState.ALPHA_BLEND,
    });
    const shaderProgram = Cesium.ShaderProgram.fromCache({
      context: context,
      vertexShaderSource: vertexShaderSource,
      fragmentShaderSource: fragmentShaderSource,
      attributeLocations: attributelocations,
    });
    const that = this;
    const uniformMap = {
      u_earthCenterWC() {
        return Cesium.Cartesian3.ZERO;
      },
      u_cloudRadius() {
        return 500.0;
      },
      u_cloudNoise3D() {
        return that._noiseTexture;
      },
      steps() {
        return 200;
      },
      threshold() {
        return 0.65;
      },
    };
    const gui = new dat.GUI();
    let viewModel = {
      steps: 200,
      threshold: 0.65,
    };

    gui
      .add(viewModel, "threshold")
      .name("threshold")
      .min(0.0)
      .max(1.0)
      .step(0.1)
      .onChange((v) => {
        uniformMap.threshold = () => v;
      });

    gui
      .add(viewModel, "steps")
      .name("steps")
      .min(0.0)
      .max(300.0)
      .step(1)
      .onChange((v) => {
        uniformMap.steps = () => v;
      });

    this.drawCommand = new Cesium.DrawCommand({
      boundingVolume: Cesium.BoundingSphere.transform(
        geometry.boundingSphere,
        Cesium.Matrix4.multiplyByTranslation(
          Cesium.Transforms.eastNorthUpToFixedFrame(
            Cesium.Cartesian3.fromDegrees(124.21936679679918, 45.85136872098397)
          ),
          new Cesium.Cartesian3(0.0, 0.0, 80.0),
          new Cesium.Matrix4()
        ),
        new Cesium.BoundingSphere()
      ),
      modelMatrix: Cesium.Matrix4.multiplyByTranslation(
        Cesium.Transforms.eastNorthUpToFixedFrame(
          Cesium.Cartesian3.fromDegrees(124.21936679679918, 45.85136872098397)
        ),
        new Cesium.Cartesian3(0.0, 0.0, 80.0),
        new Cesium.Matrix4()
      ),
      pass: Cesium.Pass.OPAQUE,
      // pass: Cesium.Pass.TRANSLUCENT,
      shaderProgram: shaderProgram,
      renderState: renderstate,
      vertexArray: this.vertexarray,
      uniformMap: uniformMap,
    });
  }

  update(frameState) {
    if (!this.drawCommand) {
      this.createDrawCommand(frameState.context);
    }
    frameState.commandList.push(this.drawCommand);
  }
}
